package cn.blnp.net.flowable.task.drools;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.persistence.deploy.DeploymentCache;
import org.flowable.engine.*;
import org.flowable.engine.impl.persistence.deploy.DeploymentManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.api.Test;
import org.kie.api.runtime.KieContainer;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>Drools规则引擎-规则任务</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/13 14:37
 */
@Slf4j
@SpringBootTest
public class DroolsTaskTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ProcessEngineConfiguration processEngineConfiguration;
    @Resource
    private KieContainer kieContainer;

    /**
     *  <h2>基本说明</h2>
     *  <hr/>
     *  <p>
     *   &emsp;&emsp;Flowable默认支持Drools规则引擎，用于执行业务规则。在Flowable中，包含业务规则的Drools规则引擎.drl文件必须与定义了业务规则服务并执行规则的流程定义
     *   一起部署，这意味着流程中使用的所有.drl文件都需要打包到流程BAR文件中。这一点与任务表单类似。
     *   &emsp;&emsp;Drools（JBoss Rules）是一个易于访问企业策略、调整及管理的开源业务规则引擎，可以实现业务代码和业务规则的分离。在工作流引擎中使用规则可极大地提高业务的
     *   可维护性。文件定义格式如下所示：
     *   <pre>
     *       {@code
     *          <businessRuleTask id="businessRuleTask1" name="业务规则任务"
     *              flowable:ruleVariablesInput="${rulesInputVariables}"
     *              flowable:resultVariable="rulesOutput"
     *              flowable:rules="rule1, rule2" flowable:exclude="true" />
     *       }
     *   </pre>
     *   <ol>
     *       <li>
     *           <b>flowable:rules </b>在Drools规则引擎.drl文件中定义的规则名称，多个规则之间用英文逗号分隔。如果不设置该属性，则执行规则文件中的全部规则
     *       </li>
     *       <li>
     *           <b style="color:red;">* flowable:ruleVariablesInput </b>输入变量，用于定义业务规则执行需要的变量，可以表示为由英文逗号分隔的多个流程变量
     *       </li>
     *       <li>
     *           <b>flowable:resultVariable </b>输出变量，用于定义业务规则执行结果变量，只能包含一个变量名。工作流引擎会将执行业务规则后返回的对象保存到对应的流程
     *           变量中。变量的值为该属性定义的变量列表。如果没有指定输出变量名称，默认会使用 {@link org.flowable.engine.rules.OUTPUT}
     *       </li>
     *       <li>
     *           <b>flowable:exclude </b>用于设置是否排除某些规则。如果设置为true，则忽略flowable:rules指定的规则，执行其他的规则；如果设置为false，则只执行
     *           flowable:rules指定的规则；如果设置为fasle的同时flowable:rules值为空，则不执行任何规则
     *       </li>
     *   </ol>
     *  </p>
     *  <p>
     *      如果想要使用自定义规则任务实现，如希望通过不同方式使用Drools，或者想使用完全不同的规则引擎，则可以通过设置BusinessRuleTask的class或expression属性实现。例如：
     *  <pre>
     *      {@code
     *          <businessRuleTask id="businessRuleTask" flowable:class="${myRuleServiceDelegate}" />
     *      }
     *  </pre>
     *  </p>
     **/
    @Test
    public void testBase() {

    }

    /**
     *  <h2>案例演示</h2>
     *  <hr/>
     *  <p>
     *      该流程为电商结算流程。其中，“折扣策略计算”为业务规则任务，用于根据消费金额匹配折扣规则计算折扣率和折扣金额。规则要求如下：
     *      <ol>
     *          <li>消费金额不足5000元时，不打折；</li>
     *          <li>消费金额满5000元且不足10 000元时，打九折；</li>
     *          <li>消费金额满10 000元时，打八折。</li>
     *      </ol>
     *      <b>流程定义文件：</b>{@link flow/businessRuleTaskProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/13/6822f6aeca4c6.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("Drools规则引擎整合案例")
                .addClasspathResource("flow/businessRuleTaskProcess.bpmn20.xml")
                .addClasspathResource("drools/DroolsTaskTest.drl")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());

        /** start **/
        DeploymentManager deploymentManager = ((SpringProcessEngineConfiguration) processEngineConfiguration).getDeploymentManager();
        DeploymentCache<Object> baseCache = deploymentManager.getKnowledgeBaseCache();
        baseCache.add(deploy.getId(), kieContainer.getKieBase());
        /**
         *  特别说明：若使用 businessRuleTask 节点，按默认方式配置的话则必须补上这一段配置。否则执行任务审批时会抛出以下异常信息：
         *  org.flowable.common.engine.api.FlowableException: deployment xxxx doesn't contain any rules
         *  推荐整合Drools时，使用 service 节点来手动配置触发规则，不推荐该方式。
         **/
        /** end **/

        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());

        //查询并完成第一个任务
        Task task = taskQuery.singleResult();
        log.info("即将完成第一个任务，当前任务名称：{}", task.getName());

        //初始化流程变量
        CostCalculation costCalculation = new CostCalculation();
        costCalculation.setOriginalTotalPrice(6666);
        Map<String, Object> varMap = MapUtil.builder(new HashMap<String, Object>())
                .put("myCostCalculation", costCalculation)
                .build();
        //办理第一个任务
        taskService.complete(task.getId(), varMap);
        //查询并打印流程变量
        List<HistoricVariableInstance> hisVars = historyService
                .createHistoricVariableInstanceQuery()
                .processInstanceId(procInst.getId()).list();
        hisVars.stream().forEach((hisVar) -> log.info("流程变量名：{}，变量值：{}",
                hisVar.getVariableName(), JSON.toJSONString(hisVar.getValue())));

        /**
         *  演示结果：
         * 2025-05-13 16:31:48.417  INFO 66252 --- [           main] c.b.n.f.task.drools.DroolsTaskTest       : 部署流程定义：b5f8aa0a-2fd4-11f0-8b6f-50ebf6ec8f6d
         * 2025-05-13 16:31:48.420  INFO 66252 --- [           main] c.b.n.f.task.drools.DroolsTaskTest       : 流程定义编号：businessRuleTaskProcess:10:b624c31e-2fd4-11f0-8b6f-50ebf6ec8f6d
         * 2025-05-13 16:31:48.471  INFO 66252 --- [           main] c.b.n.f.task.drools.DroolsTaskTest       : 即将完成第一个任务，当前任务名称：提交订单
         * 触发规则rule2：消费金额满5000元且不足10000元时，打九折
         * 2025-05-13 16:31:48.696  INFO 66252 --- [           main] c.b.n.f.t.drools.DeductionJavaDelegate   : 折扣前消费金额：6666.0，折扣率：0.9，折扣后实际消费金额：5999.400000000001
         * 2025-05-13 16:31:49.131  INFO 66252 --- [           main] c.b.n.f.task.drools.DroolsTaskTest       : 流程变量名：myCostCalculation，变量值：{"actualTotalPrice":5999.400000000001,"discountRatio":0.9,"originalTotalPrice":6666.0}
         * 2025-05-13 16:31:49.132  INFO 66252 --- [           main] c.b.n.f.task.drools.DroolsTaskTest       : 流程变量名：costCalculationResults，变量值：[{"actualTotalPrice":5999.400000000001,"discountRatio":0.9,"originalTotalPrice":6666.0}]
         **/
    }
}
